﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.IntersectionEngines
{


    [StructLayout(LayoutKind.Explicit, Size = 32)]
    public struct BvhNode
    {
        [FieldOffset(0)]
        public AABB Bounds;
        [FieldOffset(24)]
        public UInt32 SkipIndex;
        [FieldOffset(28)]
        public UInt32 PrimitiveIndex;

        //[FieldOffset(28)]
        //public byte Axis;

        public void SetSkipIndex(uint skipIndex)
        {
            this.SkipIndex = skipIndex;
        }

    }

    public abstract class GenericBvhGenerator<TPrimitive>
    {
        protected const int PrimitivesInNode = 1;
        public int TraversalStackSize = 64;
        public const uint NoHit = RayBuffer.NotHit;

        public Node RootNode;


        public class Node
        {
            public AABB Bound;
            public Node Left, Right;
            public List<TPrimitive> Primitives;
            public int PrimitiveIndexStart;
        }

        public BvhNode[] nodes;


        protected abstract AABB CalcBound(ref TPrimitive prim);

        private void LinearizeBVH()
        {
            nodes = new BvhNode[bvhNodesCount];

            this.BuildArray(RootNode, 0);
            //this.FlattenBVHTree(RootNode, ref offset);
        }

        private int BuildArray(Node node, int offset)
        {
            var nd = node;
            while (nd != null)
            {
                var firstOffset = offset;
                var p = new BvhNode()
                    {
                        Bounds = nd.Bound,
                        PrimitiveIndex = nd.Primitives != null ? (uint)nd.PrimitiveIndexStart : NoHit,
                    };
                offset = BuildArray(nd.Left, offset + 1);
                p.SetSkipIndex((uint)offset);
                nodes[firstOffset] = p;
                nd = nd.Right;
            }
            return offset;
        }

        public void BuildTree(List<TPrimitive> primitives, int start, int end)
        {
            Tracer.TraceLine("Building accelleration {0} triangles", primitives.Count);

            if (start - end == 0)
                return;
            AABB bound = primitives.GetRange(start, end - start).Select(prim =>CalcBound(ref prim)).Aggregate((b1, b2) => b1.Union(b2));
            RootNode = new Node() { Bound = bound };
            bvhNodesCount++;
            if ((end - start) <= PrimitivesInNode)
            {
                RootNode.Primitives = primitives.GetRange(start, (end - start));
                RootNode.PrimitiveIndexStart = start;
                return;
            }



            RootNode.Left = this.BuildSubTree(primitives, start, (start + end) / 2);
            RootNode.Right = this.BuildSubTree(primitives, (start + end) / 2, end);

            Tracer.TraceLine("Linearize BVH");
            this.LinearizeBVH();
            Tracer.TraceLine("Complete");

        }


        private Node BuildSubTree(List<TPrimitive> primitives, int start, int end)
        {
            if (start - end == 0)
                return null;
            AABB bound = primitives.GetRange(start, end - start).Select(prim => CalcBound(ref prim)).Aggregate((b1, b2) => b1.Union(b2));
            var node = new Node() { Bound = bound };
            bvhNodesCount++;
            if ((end - start) == PrimitivesInNode)
            {
                node.Primitives = primitives.GetRange(start, (end - start));
                node.PrimitiveIndexStart = start;
                return node;
            }
            var mid = (start + end) / 2;
            node.Left = this.BuildSubTree(primitives, start, mid);
            node.Right = this.BuildSubTree(primitives, mid + 1, end);
            return node;
        }



        #region IAccellerationStructure Members

        private int bvhNodesCount;
        public void Init(RayEngineScene scene, TPrimitive[] primitives)
        {
            this.BuildTree(primitives.ToList(), 0, primitives.Length);
            Tracer.TraceLine("Tree is builded, {0} nodes total", bvhNodesCount);
        }

        #endregion
        /*
        public bool Intersect(RayInfo ray)
        {
#if LBVH
            return BVHAccel_SIntersect(ray);

            //return this.ShadowIntersectBVH(ray);
#else
            return IntersectNode(RootNode, ray);
#endif
        }



        public bool Intersect(RayInfo ray, out IntersectionInfo intersection)
        {
#if LBVH
            intersection = new IntersectionInfo() { Distance = 1000000f };
            return BVHAccel_Intersect(ray, ref intersection);
            //return IntersectBVH(ray, ref intersection);
#else
            return IntersectNode(RootNode, ray, out intersection);
#endif
        }

        private bool IntersectNode(BoundingVolumeHierarchyAcceleration.Node node, RayInfo ray, out IntersectionInfo intersection)
        {
            if (node == null)
            {
                intersection = null;
                return false;
            }
            float t0, t1 = 0f, dist = float.MaxValue, u = 0f, v = 0f;
            if (BVHAccellerationStructure.IntersectBox(node.Bound, ray))
            {
                //ray.MinT = t0;
                //ray.MaxT = t1;

                if (node.Primitives != null && node.Primitives.Count > 0)
                {
                    for (int i = 0; i < node.Primitives.Count; i++)
                    {
                        if (node.Primitives[i].Intersect(ref sceneVertices, ref ray, ref t1, ref u, ref v) && dist > t1)
                        {
                            var triangleMesh = node.Primitives[i].Owner;
                            intersection = new IntersectionInfo();
                            dist = t1;
                            intersection.Distance = dist;
                            intersection.Geometry = node.Primitives[i];
                            intersection.Point = ray.Point(dist);
                            intersection.Material = materialProvider.Get(triangleMesh.MaterialName);

                            var gnormal =
                                intersection.Geometry.InterpolateMeshNormal(u, v).Normalize();
                            //InterpolateNormal(ref sceneVertices, u, v).Normalize();
                            //triangleMesh.GetTriangleNormal(i, 0);
                            //triangleMesh.InterpolateTriangleNormal(i, u, v).Normalize();
                            Normal shadeN;

                            if (Vector.Dot(ref ray.Dir, ref gnormal) >= 0f)
                                shadeN = -gnormal;
                            else
                                shadeN = gnormal;

                            intersection.ShadingNormal = shadeN;
                            intersection.Normal = gnormal;
                            //                                    triangleMesh.InterpolateTriangleNormal(i, u, v);
                            //triangleMesh.triangles[i].InterpolateNormal(ref scene.Vertices, u, v).Normalize();
                            //triangleMesh.triangles[i].InterpolateNormal(ref scene.Vertices, u, v).Normalize();
                            //triangleMesh.triangles[i].ComputeNormal(ref scene.Vertices).Normalize();

                            intersection.TexCoords = new UV(u, v);
                            //triangleMesh.InterpolateTriUV(i, u, v);
                            return true;
                        }
                    }
                }

                IntersectionInfo li, ri;
                var leftIntersect = IntersectNode(node.Left, ray, out li);
                var rIntersect = IntersectNode(node.Right, ray, out ri);
                if (leftIntersect & rIntersect)
                {
                    intersection = li.Distance > ri.Distance ? ri : li;
                    return true;
                }
                if (leftIntersect)
                {
                    intersection = li;
                    return true;
                }
                if (rIntersect)
                {
                    intersection = ri;
                    return true;
                }

            }
            intersection = null;
            return false;
        }


        private bool IntersectNode(BoundingVolumeHierarchyAcceleration.Node node, RayInfo ray)
        {
            if (node == null)
            {
                return false;
            }
            float t1 = 0f, u = 0f, v = 0f;
            if (BVHAccellerationStructure.IntersectBox(node.Bound, ray))
            {
                if (node.Primitives != null && node.Primitives.Count > 0)
                {
                    for (int i = 0; i < node.Primitives.Count; i++)
                    {
                        if (node.Primitives[i].Intersect(ref sceneVertices, ref ray, ref t1, ref u, ref v))
                        {
                            return true;
                        }
                    }
                }
                return IntersectNode(node.Left, ray) || IntersectNode(node.Right, ray);
            }
            return false;
        }

        #endregion




        #region Linear BVH

        private bool IntersectBVH(RayInfo ray, ref IntersectionInfo sp)
        {
            if (sp == null)
            {
                sp = new IntersectionInfo();
            }
            if (nodes.Length == 0)
                return false;
            TriangleMesh lastHitMesh = null;
            int lastHitIndex = -1, triangleIndex = -1;
            float u = 0f, v = 0f;
            bool hit = false;
            Vector invDir = ray.InvDir;
            //new Vector(1f / ray.Dir.x, 1f / ray.Dir.y, 1f / ray.Dir.z);
            var dirIsNeg = new[] { invDir.x < 0, invDir.y < 0, invDir.z < 0 };
            int todoOffset = 0, nodeNum = 0;
            int[] todo = new int[TraversalStackSize];
            var dist = float.MaxValue;
            while (true)
            {
                float t1 = 0f;
                if (BVHAccellerationStructure.IntersectBox(nodes[nodeNum].Bounds, ray))
                {
                    //if (node.bbox.Intersect(vray, out t0, out t2)) {
                    if (nodes[nodeNum].PrimitiveIndex != NoHit)
                    {
                        if (triangles[nodes[nodeNum].PrimitiveIndex].Intersect(ref sceneVertices, ref ray, ref t1, ref u, ref v))
                        {
                            if (dist > t1)
                            {
                                triangleIndex = nodes[nodeNum].PrimitiveIndex;
                                lastHitMesh = triangles[nodes[nodeNum].PrimitiveIndex].Owner;
                                lastHitIndex = triangles[nodes[nodeNum].PrimitiveIndex].Index;
                                dist = t1;
                                sp.Point = ray.Point(dist);
                                sp.Geometry = triangles[nodes[nodeNum].PrimitiveIndex];
                                sp.Distance = dist;
                                hit = true;
                            }
                        }
                        if (todoOffset == 0)
                            break;
                        nodeNum = todo[--todoOffset];
                    }
                    else
                    {
                        if (dirIsNeg[nodes[nodeNum].Bounds.MaximumExtent()])
                        {
                            todo[todoOffset++] = nodeNum + 1;
                            nodeNum = nodes[nodeNum].SkipIndex;
                        }
                        else
                        {
                            todo[todoOffset++] = nodes[nodeNum].SkipIndex;
                            nodeNum = nodeNum + 1;
                        }
                    }
                }
                else
                {
                    if (todoOffset == 0)
                        break;
                    nodeNum = todo[--todoOffset];
                }
            }

            if (hit)
            {
                sp.Material = materialProvider.Get(lastHitMesh.MaterialName);
                var gnormal = triangles[triangleIndex].InterpolateMeshNormal(u, v).Normalize();
                Normal shadeN;

                if (Vector.Dot(ref ray.Dir, ref gnormal) >= 0f)
                    shadeN = -gnormal;
                else
                    shadeN = gnormal;

                sp.ShadingNormal = shadeN;
                sp.Normal = gnormal;
                //triangles[node.primOffset + i].InterpolateNormal(ref sceneModel.Vertices, u, v).Normalize();
                sp.TexCoords = lastHitMesh.InterpolateTriUV(lastHitIndex, u, v);
                return true;
            }

            return false;
        }

        private bool ShadowIntersectBVH(RayInfo ray)
        {
            if (nodes.Length == 0)
                return false;
            bool hit = false;
            Vector invDir = ray.InvDir;
            //new Vector(1f / ray.Dir.x, 1f / ray.Dir.y, 1f / ray.Dir.z);
            var dirIsNeg = new[] { invDir.x < 0, invDir.y < 0, invDir.z < 0 };
            int todoOffset = 0, nodeNum = 0;
            int[] todo = new int[TraversalStackSize];
            while (true)
            {
                float t1 = 0f, u = 0f, v = 0f;
                if (BVHAccellerationStructure.IntersectBox(nodes[nodeNum].Bounds, ray))
                {
                    if (nodes[nodeNum].PrimitiveIndex != NoHit)
                    {
                        if (triangles[nodes[nodeNum].PrimitiveIndex].Intersect(ref sceneVertices, ref ray, ref t1, ref u,
                                                                               ref v))
                        {
                            hit = true;
                            break;
                        }

                        if (todoOffset == 0)
                            break;
                        nodeNum = todo[--todoOffset];
                    }
                    else
                    {
                        if (dirIsNeg[nodes[nodeNum].Bounds.MaximumExtent()])
                        {
                            todo[todoOffset++] = nodeNum + 1;
                            nodeNum = nodes[nodeNum].SkipIndex;
                        }
                        else
                        {
                            todo[todoOffset++] = nodes[nodeNum].SkipIndex;
                            nodeNum = nodeNum + 1;
                        }
                    }
                }
                else
                {
                    if (todoOffset == 0)
                        break;
                    nodeNum = todo[--todoOffset];
                }
            }

            return hit;
        }


        private bool BVHAccel_Intersect(RayInfo ray, ref IntersectionInfo sp)
        {
            float u = 0f, v = 0f, t1 = 0f;
            int currentNode = 0; // Root Node
            int stopNode = nodes.Length; // Non-existent
            bool hit = false;
            TriangleMesh lastHitMesh = null;
            int lastHitIndex = -1, triangleIndex = -1;
            while (currentNode < stopNode)
            {
                if (BVHAccellerationStructure.IntersectBox(nodes[currentNode].Bounds, ray))
                {
                    if (nodes[currentNode].PrimitiveIndex != NoHit)
                    {
                        if (triangles[nodes[currentNode].PrimitiveIndex].Intersect(ref sceneVertices, ref ray, ref t1, ref u, ref v))
                        {
                            if (t1 < sp.Distance)
                            {
                                hit = true; // Continue testing for closer intersections

                                sp.Point = ray.Point(t1);
                                sp.Geometry = triangles[nodes[currentNode].PrimitiveIndex];
                                sp.Distance = t1;
                                triangleIndex = nodes[currentNode].PrimitiveIndex;
                                lastHitMesh = triangles[nodes[currentNode].PrimitiveIndex].Owner;
                                lastHitIndex = triangles[nodes[currentNode].PrimitiveIndex].Index;
                            }
                        }
                    }
                    currentNode++;
                }
                else
                    currentNode = nodes[currentNode].SkipIndex;
            }

            if (hit)
            {
                sp.Material = materialProvider.Get(lastHitMesh.MaterialName);
                var gnormal = triangles[triangleIndex].InterpolateMeshNormal(u, v).Normalize();
                Normal shadeN;

                if (Vector.Dot(ref ray.Dir, ref gnormal) >= 0f)
                    shadeN = -gnormal;
                else
                    shadeN = gnormal;

                sp.ShadingNormal = shadeN;
                sp.Normal = gnormal;
                //triangles[node.primOffset + i].InterpolateNormal(ref sceneModel.Vertices, u, v).Normalize();
                sp.TexCoords = lastHitMesh.InterpolateTriUV(lastHitIndex, u, v);
                return true;
            }


            return hit;
        }
        private bool BVHAccel_SIntersect(RayInfo ray)
        {
            float u = 0f, v = 0f, t1 = 0f;
            int currentNode = 0; // Root Node
            int stopNode = nodes.Length; // Non-existent
            while (currentNode < stopNode)
            {
                if (BVHAccellerationStructure.IntersectBox(nodes[currentNode].Bounds, ray))
                {
                    if (nodes[currentNode].PrimitiveIndex != NoHit)
                    {
                        if (triangles[nodes[currentNode].PrimitiveIndex].Intersect(ref sceneVertices, ref ray, ref t1, ref u, ref v))
                        {
                            return true;
                        }
                    }
                    currentNode++;
                }
                else
                    currentNode = nodes[currentNode].SkipIndex;
            }
            return false;

        }

        #endregion
        */

    }


}